<?php

/**
 * Kaeru Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2009, Jonathan Hedrén
 * @author	   Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

namespace Kaeru\Model\Dao;

use \Kaeru\Core\Filters;
use \Kaeru\Core\Validators;
use \Kaeru\Language\Object;
use \Kaeru\Model\AbstractModel;
use \Kaeru\Model\ModelGenerator;
use \Kaeru\Storage\StorageFactory;

/**
 * @see \Kaeru\Model\AbstractModel
 */
require_once 'Kaeru/Model/AbstractModel.php';

/**
 * @see \Kaeru\Model\Property\PropertyProxy
 */
require_once 'Kaeru/Model/Property/PropertyProxy.php';

/**
 * @see \Kaeru\Model\Dao\IDao
 */
require_once 'Kaeru/Model/Dao/IDao.php';

/**
 * @abstract
 */
abstract class AbstractDao extends Object implements IDao
{
	
    /**
     * The cache storage handler.
     *
     * @var \Kaeru\Storage\Cache\Handler\HandlerInterface
     */
    protected $cacheStorage;

    /**
     * The name/identifier of the cache storage to be used (if any).
     *
     * @var string
     */
    protected $cacheStorageName;

    /**
     * A DaoFactory to use for the property proxy.
     *
     * @var \Kaeru\Model\Dao\DaoFactory
     */
    protected $daoFactory;

    /**
     * An array of filters to apply when the filter method is called on a model.
     *
     * 'property' => array('filter1', 'filter2', 'filter3')
     *
     * @var array
     */
    protected $filters = array();

    /**
     * The name of the class from which the model class will inherit from.
     * 
     * @var string
     */
    protected $modelBaseClass = '\\Kaeru\\Model\\AbstractModel';

    /**
     * The class name of the models that the DAO instance will create.
     *
     * @var string
     */
    protected $modelClass;

    /**
     * The properties of the model. This array will be used when generating the
     * model.
     *
     * @var array
     */
    protected $modelProperties = array();

    /**
     * The instance of the property proxy used for lazy loading.
     *
     * @var \Kaeru\Model\Property\PropertyProxyInterface
     */
    protected $propertyProxy;

    /**
     * The class name of the class to use as property proxy. The specified class
     * must \Kaeru\Model\Property\PropertyProxyInterface.
     *
     * @var string
     */
    protected $propertyProxyClass = '\Kaeru\Model\Property\PropertyProxy';

    /**
     * The configuration for the property proxy. The array key will be used when
     * generating a model (a protected property 'key' will be created).
     *
     * Example:
     * 'parent' => array(
     *		'dao' => 'Parent',
     *		'method' => 'get',
     *		'params' => array(
     *			'@id'
     *		)
     *	),
     * 'children' => array(
     *		'dao' => 'Child',
     *		'method' => 'getCollectionByParent',
     *		'params' => array(
     *			'@id',
     *			array(
     *				'limit' => 10,
     *				'orderBy' => 'id',
     *				'orderDir' => 'ASC'
     *			)
     *		)
     * )
     *
     * @var array
     */
    protected $propertyProxySettings = array();
		
    /**
     * The storage to be used.
     *
     * @var mixed
     */
    protected $storage;

    /**
     * The storage factory.
     *
     * @var \Kaeru\Storage\StorageFactory
     */
    protected $storageFactory;

    /**
     * The name/identifier of the storage to be used.
     *
     * @var string
     */
    protected $storageName = 'default';

    /**
     * An array of validators to use when validating a model.
     *
     * 'property' => array('validator1', 'validator2', 'validator3')
     *
     * @var array
     */
    protected $validators = array();

    /**
     * Constructor.
     *
     * @param \Kaeru\Storage\StorageFactory $storageFactory
     * @return \Kaeru\Model\Dao\AbstractDao
     * @todo Passing the daoFactory to the property proxy via this constructor
     *       isn't that nice... = find out a better way to supply the property
     *       proxy with a DaoFactory.
     */
    public function __construct(StorageFactory $storageFactory,
        DaoFactory $daoFactory)
    {
        $this->daoFactory = $daoFactory;

        $this->storage = $storageFactory->get($this->storageName);
//        $this->saveStorage = $storageFactory->get($this->saveStorageName);

        if ($this->cacheStorageName !== null) {
            $this->cacheStorage = $storageFactory->get($this->cacheStorageName);
        }

        $this->storageFactory = $storageFactory;

        return $this;
    }

    /**
     * Creates a new model. If no model class is found a
     * model class file will be generated.
     *
     * @return \Kaeru\Model\AbstractModel
     */
    protected function createModel($modelClass, array $modelProperties,
        array $proxyProperties = null)
    {
        $model = null;
        try {
            $model = new $modelClass();
        } catch (\Exception $e) {
            require_once 'Kaeru/Model/ModelGenerator.php';
            //TODO: Set namespace...
            ModelGenerator::setBaseModel($this->modelBaseClass);
            ModelGenerator::generateModel(
                $modelClass,
                $modelProperties,
                $proxyProperties
            );
            try {
                $model = new $modelClass();
            } catch (\Exception $e) {
                throw $e;
            }
        }
        return $model;
    }

    /**
     * Applies any property proxies to the model.
     *
     * @param \Kaeru\Model\AbstractModel $model
     * @return void
     */
    protected function applyPropertyProxies(AbstractModel $model)
    {
        if (count($this->propertyProxySettings)) {
            if ($this->propertyProxy === null) {
                $propertyProxyClass = $this->propertyProxyClass;
                $this->propertyProxy = new $propertyProxyClass(
                    $this->propertyProxySettings,
                    $this->daoFactory
                );
            }
            foreach ($this->propertyProxySettings as $property => $setting) {
                $model->$property = $this->propertyProxy;
            }
        }
    }

    /**
     * Filters the model properties according to the filters specified in
     * $this->filters.
     *
     * @param \Kaeru\Model\AbstractModel $model
     * @return \Kaeru\Model\Dao\AbstractDao
     */
    public function filter(AbstractModel $model)
    {
        foreach (get_object_vars($model) as $property => $propertyValue) {
            if (isset($this->filters[$property])) {
                $filters = $this->filters[$property];
                foreach ($filters as $filter) {
                    $model->$property = Filters::$filter($propertyValue);
                }
            }
        }
        return $this;
    }
		
    /**
     * Creates and returns an empty model.
     *
     * @param boolean $withPropertyProxies
     * @return \Kaeru\Model\AbstractModel
     */
    public function create($withPropertyProxies = false)
    {
        if ($this->modelClass == null) {
            throw new \Exception('$modelClass must be set.');
        }

        $model = $this->createModel(
            $this->modelClass,
            $this->modelProperties,
            array_keys($this->propertyProxySettings)
        );

        if ($withPropertyProxies) {
            $this->applyPropertyProxies($model);
        }
        
        return $model;
    }

    /**
     * @deprecated
     * @return <type>
     */
    public function getEmptyWithPropertyProxies()
    {
        $model = $this->getEmpty();
        $this->applyPropertyProxies($model);
        return $model;
    }

    /**
     * Sets the name of the class to use as property proxy. The class must
     * implement the Kaeru\Model\Property\PropertyProxyInterface interface.
     *
     * @param string $propertyProxyClass
     * @return \Kaeru\Model\Dao\AbstractDao
     */
    public function setPropertyProxyClass($propertyProxyClass)
    {
        $this->propertyProxyClass = $propertyProxyClass;
        return $this;
    }

    /**
     * Sets the property proxy settings.
     *
     * @param array $settings
     * @return \Kaeru\Model\Dao\AbstractDao
     */
    public function setPropertyProxySettings(array $settings)
    {
        $this->propertyProxySettings = $settings;
        return $this;
    }

    /**
     * Sets a single property proxy's settings.
     *
     * @param string $property
     * @param array $settings
     * @return \Kaeru\Model\Dao\AbstractDao
     */
    public function setPropertyProxySetting($property, array $settings)
    {
        $this->propertyProxySettings[$property] = $settings;
        return $this;
    }

    /**
     * Sets the model class to use when factoring models.
     *
     * @return \Kaeru\Model\Dao\AbstractDao
     * @param string $modelClass
     */
    public function setModelClass($modelClass)
    {
        $this->modelClass = $modelClass;
        return $this;
    }

    /**
     * Validates the model according to the validators specified in
     * $this->validators.
     *
     * @param Kaeru\Model\AbstractModel $model
     * @return \Kaeru\Model\Dao\AbstractDao
     */
    public function validate(AbstractModel $model)
    {
        $model->setIsValid();
        foreach (get_object_vars($model) as $property => $propertyValue) {
            if (isset($this->validators[$property])) {
                $validators = $this->validators[$property];
                foreach ($validators as $validator) {
                    if (!$model->isValid()) {
                        continue;
                    }
                    $model->setValid(Validators::$validator($propertyValue));
                }
                $model->setValidationMessages($property, Validators::getMessages());
            }
        }
        return $this;
    }
}
